package com.radar.io.standard;

import cn.hutool.core.date.DateUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.radar.algorithm.MathCommon;
import com.radar.common.CinradType;
import com.radar.common.GisConstants;
import com.radar.common.ProductType;
import com.radar.exception.RadarDecodeError;
import com.radar.io.cinrad.BaseCinrad;
import com.radar.utils.BitConverter;
import ucar.ma2.ArrayByte;
import ucar.ma2.ArrayDouble;
import ucar.ma2.DataType;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.Variable;

import java.io.IOException;
import java.util.*;
import java.util.stream.IntStream;

/**
 * @author: fy-wbj
 * @description: standard 雷达
 * @date: 2020/10/10
 */
public class RadarStandard extends BaseCinrad {

    private double[] ele;

    private double[][] azi;

    private double reso;

    public RadarStandard(String file, CinradType type, String code) {
        super(file, type, code);
    }

    public RadarStandard() {
        super();
    }

    static Log log = LogFactory.get();

    public void parse(byte[] bytes) throws RadarDecodeError {
        try {
            RadarStandard.RadarHeader header = new RadarStandard.RadarHeader();
            int index = 0;
            // parse radar header
            byte[] bytdata = new byte[header.len];
            System.arraycopy(bytes, index, bytdata, 0, bytdata.length);
            header.parse(bytdata);
            index += bytdata.length;
            // parse radar site
            RadarSite radarSite = new RadarSite();
            bytdata = new byte[radarSite.len];
            System.arraycopy(bytes, index, bytdata, 0, bytdata.length);
            radarSite.parse(bytdata);
            index += bytdata.length;
            // parse radar task
            TaskConfig taskConfig = new TaskConfig();
            bytdata = new byte[taskConfig.len];
            System.arraycopy(bytes, index, bytdata, 0, bytdata.length);
            taskConfig.parse(bytdata);
            index += bytdata.length;
//            this.vcp = Integer.parseInt(taskConfig.taskName.replace("VCP", ""));
            this.vcp = 21;
            int layer = taskConfig.cutNumber;
            long sec = taskConfig.scanStartTime;
            this.dateTime = DateUtil.offsetSecond(DateUtil.parse("1970-01-01").toJdkDate(), (int) sec).toJdkDate();
            if (taskConfig.scanType == 2) {
                this.scanType = ScanType.RHI;
            } else {
                this.scanType = ScanType.PPI;
            }
            this.ele = new double[layer];
            for (int i = 0; i < this.ele.length; i++) {
                this.ele[i] = BitConverter.toSingle(bytes, index + 24);
                this.reso = BitConverter.toInt32(bytes, index + 12 * 4);
                index += 256;
            }
            Map<Integer, HashMap<String, List<List<Integer>>>> data = new HashMap<>();
            Map<Integer, HashMap<String, List<Double>>> aux = new HashMap<>();
            while (true) {
                if (index + 64 >= bytes.length) {
                    break;
                }
                RadialHeader radialHeader = new RadialHeader();
                bytdata = new byte[radialHeader.len];
                System.arraycopy(bytes, index, bytdata, 0, bytdata.length);
                radialHeader.parse(bytdata);
                index += bytdata.length;
                if (radialHeader.zipType.equals("1")) {
                    throw new RadarDecodeError("LZO compressed file is not supported");
                }
                int ele_num = radialHeader.elevationNumber - 1;
                if (!data.containsKey(ele_num)) {
                    data.put(ele_num, new HashMap<>());
                    aux.put(ele_num, new HashMap<>());
                    aux.get(ele_num).put("azimuth", new ArrayList<>());
                    aux.get(ele_num).put("elevation", new ArrayList<>());
                }
                aux.get(ele_num).get("azimuth").add(radialHeader.azimuth);
                aux.get(ele_num).get("elevation").add(radialHeader.elevation);
                for (int i = 0; i < radialHeader.momentNumber; i++) {
                    MomentHeader momentHeader = new MomentHeader();
                    bytdata = new byte[momentHeader.len];
                    System.arraycopy(bytes, index, bytdata, 0, bytdata.length);
                    momentHeader.parse(bytdata);
                    index += bytdata.length;
                    DtypeCorr dtypeCorr = DtypeCorr.find(momentHeader.dataType);
                    if (dtypeCorr == null) {
                        log.warn("Data type {} not understood, skipping", momentHeader.dataType);
                        continue;
                    }
                    if (!data.get(ele_num).containsKey(dtypeCorr.name())) {
                        data.get(ele_num).put(dtypeCorr.name(), new ArrayList<>());
                    }
                    List<Integer> d = new ArrayList<>();
                    for (int z = 0; z < momentHeader.blockLength / momentHeader.binLength; z++) {
                        if (momentHeader.binLength == 1) {
                            d.add((0xff & bytes[index]));
                            index++;
                        } else if (momentHeader.binLength == 2) {
                            d.add((BitConverter.toInt16(bytes, index) & 0x0FFFF));
                            index += 2;
                        } else if (momentHeader.binLength == 4) {
                            d.add(BitConverter.toInt32(bytes, index));
                            index += 4;
                        }
                    }
                    data.get(ele_num).get(dtypeCorr.name()).add(d);
                    if (!aux.get(ele_num).containsKey(dtypeCorr.name())) {
                        aux.get(ele_num).put(dtypeCorr.name(), Arrays.asList(Double.valueOf(momentHeader.scale), Double.valueOf(momentHeader.offset)));
                    }
                }
                if (radialHeader.radialState == 4 || radialHeader.radialState == 6) {
                    // End scan
                    break;
                }
            }
            int maxAzi = aux.keySet().stream().mapToInt(i -> aux.get(i).get("azimuth").size()).max().getAsInt();
            azi = new double[layer][maxAzi];
            MathCommon.fill(this.azi);
            for (int i : aux.keySet()) {
                double[] z = new double[maxAzi];
                List<Double> azimuth = aux.get(i).get("azimuth");
                MathCommon.fill(z);
                IntStream.range(0, azimuth.size()).forEach(x -> z[x] = azimuth.get(x));
                this.azi[i] = z;
            }
            this.initNetcdfTemp();
            this.writeBasicData(data, aux);
        } catch (Exception e) {
            log.error(e);
            e.printStackTrace();
            throw new RadarDecodeError("standard 数据解析异常");
        }
    }

    @Override
    public double[] getElevation() {
        return ele;
    }

    @Override
    public int[] getElevationIndex(ProductType type) {

        if (type == ProductType.REF) {
            return IntStream.range(0, ele.length).filter(i -> i != 1 && i != 3).toArray();
        } else {
            return IntStream.range(0, ele.length).filter(i -> i != 0 && i != 2).toArray();
        }
    }

    @Override
    public double[][] getAzimuth() {
        return azi;
    }

    @Override
    public double getResolution(ProductType type) {
        return reso;
    }

    public void writeBasicData(Map<Integer, HashMap<String, List<List<Integer>>>> data, Map<Integer, HashMap<String, List<Double>>> aux) throws IOException {
        int length = data.entrySet().stream().mapToInt(z -> z.getValue().entrySet().stream().mapToInt(y -> y.getValue().stream().mapToInt(c -> c.size()).max().getAsInt()).max().getAsInt()).max().getAsInt();
        write.setRedefineMode(true);
        Variable v1;
        if ((v1 = this.netcdf.findVariable("distance")) == null) {
            write.addDimension(null, "distance", length);
            v1 = write.addVariable(null, "distance", DataType.DOUBLE, "distance");
            v1.addAttribute(new Attribute("units", "m"));
            v1.addAttribute(new Attribute("resolution", reso));
        }
        // write distance
        ArrayDouble distances = new ArrayDouble.D1(length);
        IntStream.range(0, length).forEach(i -> distances.setDouble(i, (i + 1) * reso));
        v1.setCachedData(distances);

        // write elevation
        Set<String> keys = new HashSet<>();
        data.entrySet().stream().forEach(i -> i.getValue().keySet().stream().forEach(z -> keys.add(z)));
        for (String key : keys) {
            ProductType productType;
            try {
                productType = ProductType.valueOf(key);
            } catch (java.lang.IllegalArgumentException e) {
                log.warn("No enum constant com.radar.common.ProductType {}", key);
                continue;
            }
            List<Dimension> dimensions = new ArrayList<>();
            dimensions.add(netcdf.findDimension("elevation"));
            dimensions.add(netcdf.findDimension("azimuth"));
            dimensions.add(netcdf.findDimension("distance"));
            byte[][][] d = new byte[data.size()][azi[0].length][length];
            MathCommon.fill(d);
            for (int i = 0; i < data.size(); i++) {
                if (data.get(i) == null || !data.get(i).containsKey(key)) {
                    continue;
                }
                double scale = aux.get(i).get(key).get(0);
                double offset = aux.get(i).get(key).get(1);
                for (int j = 0; j < azi[0].length; j++) {
                    if (azi[i][j] == GisConstants.UNDEF) {
                        continue;
                    }
                    List<Integer> bytes = data.get(i).get(key).get(j);
                    for (int z = 0; z < bytes.size(); z++) {
                        int c = bytes.get(z);
                        if(c > 5){
                            d[i][j][z] = MathCommon.Code(((c - offset) / scale),productType);
                        }
                    }
                }
            }
            if (netcdf.findVariable(productType.name()) == null) {
                Variable variable = productType.writeVariable(write, netcdf, dimensions);
                ArrayByte array = MathCommon.toByteArray(d);
                variable.setCachedData(array);
            }
        }
    }

    public static boolean isStandardRadar(byte[] bytes) throws Exception {
        RadarStandard radarStandard = new RadarStandard();
        RadarStandard.RadarHeader header = radarStandard.new RadarHeader();
        int index = 0;
        byte[] bytdata = new byte[header.len];
        System.arraycopy(bytes, index, bytdata, 0, bytdata.length);
        header.parse(bytdata);
        return header == null ? false : header.magicNumber == 0x4D545352;
    }

    public enum DtypeCorr {
        TREF(1), REF(2), VEL(3), SW(4), SQI(5), CPA(6), ZDR(7), LDR(8), RHO(9), PHI(10),
        KDP(11), CP(12), HCL(14), CF(15), SNRH(16), SNRV(17), Zc(32), Vc(33), Wc(34), ZDRc(35);
        private int id;

        public static DtypeCorr find(int id) {
            for (DtypeCorr dtypeCorr : DtypeCorr.values()) {
                if (dtypeCorr.id == id) {
                    return dtypeCorr;
                }
            }
            return null;
        }

        DtypeCorr(int id) {
            this.id = id;
        }
    }

    //region 原始数据文件

    public class RadarHeader {
        public int len = 32;
        private int magicNumber;
        private int majorVersion;
        private int minorVersion;
        private int genericType;
        private int productType;
        private String res1;

        public void parse(byte[] b) throws Exception {
            int index = 0;
            magicNumber = BitConverter.toInt32(b, index);
            index += 4;

            majorVersion = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            minorVersion = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;

            genericType = BitConverter.toInt32(b, index);
            index += 4;
            productType = BitConverter.toInt32(b, index);
            index += 4;

            res1 = BitConverter.toString(Arrays.copyOfRange(b, index, index + 16));
        }
    }

    public class RadarSite {
        public int len = 128;
        private String siteCode;
        private String siteName;
        private double Latitude;
        private double Longitude;
        private int antennaHeight;
        private int groundHeight;
        private double frequency;
        private double beamWidthHori;
        private double beamWidthVert;
        private int RDAVersion;
        private int radarType;
        private int antennaGain;
        private int transLoss;
        private int recvLoss;
        private int otherLoss;
        private String res1;

        public void parse(byte[] b) throws Exception {
            int index = 0;
            siteCode = new String(Arrays.copyOfRange(b, index, index + 8), "ascii");
            siteCode = siteCode.replace("\u0000", "");
            index += 8;
            siteName = BitConverter.toString(Arrays.copyOfRange(b, index, index + 32));
            index += 32;

            Latitude = BitConverter.toSingle(b, index);
            index += 4;
            Longitude = BitConverter.toSingle(b, index);
            index += 4;

            antennaHeight = BitConverter.toInt32(b, index);
            index += 4;
            groundHeight = BitConverter.toInt32(b, index);
            index += 4;

            frequency = BitConverter.toSingle(b, index);
            index += 4;
            beamWidthHori = BitConverter.toSingle(b, index);
            index += 4;
            beamWidthVert = BitConverter.toSingle(b, index);
            index += 4;

            RDAVersion = BitConverter.toInt32(b, index);
            index += 4;
            radarType = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            antennaGain = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            transLoss = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            recvLoss = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            otherLoss = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;

            res1 = BitConverter.toString(Arrays.copyOfRange(b, index, index + 46));
            index += 46;
        }
    }

    public class TaskConfig {
        int len = 256;
        private String taskName;
        private String taskDsc;
        private int polarType;
        private int scanType;
        private int pulseWidth;
        private int scanStartTime;
        private int cutNumber;
        private double horiNoise;
        private double vertNoise;
        private double horiCali;
        private double vertCali;
        private double horiTmp;
        private double vertTmp;
        private double ZDRCali;
        private double PHIDPCali;
        private double LDRCali;
        private String res3;

        public void parse(byte[] b) throws Exception {
            int index = 0;
            taskName = new String(Arrays.copyOfRange(b, index, index + 32), "ascii");
            taskName = taskName.replace("\u0000", "");
            index += 32;
            taskDsc = BitConverter.toString(Arrays.copyOfRange(b, index, index + 128));
            index += 128;

            polarType = BitConverter.toInt32(b, index);
            index += 4;
            scanType = BitConverter.toInt32(b, index);
            index += 4;
            pulseWidth = BitConverter.toInt32(b, index);
            index += 4;
            scanStartTime = BitConverter.toInt32(b, index);
            index += 4;
            cutNumber = BitConverter.toInt32(b, index);
            index += 4;

            horiNoise = BitConverter.toSingle(b, index);
            index += 4;
            vertNoise = BitConverter.toSingle(b, index);
            index += 4;
            horiCali = BitConverter.toSingle(b, index);
            index += 4;
            vertCali = BitConverter.toSingle(b, index);
            index += 4;
            horiTmp = BitConverter.toSingle(b, index);
            index += 4;
            vertTmp = BitConverter.toSingle(b, index);
            index += 4;
            ZDRCali = BitConverter.toSingle(b, index);
            index += 4;
            PHIDPCali = BitConverter.toSingle(b, index);
            index += 4;
            LDRCali = BitConverter.toSingle(b, index);
            index += 4;

            res3 = BitConverter.toString(Arrays.copyOfRange(b, index, index + 40));
            index += 40;
        }

    }

    public class RadialHeader {
        private int len = 64;
        private int radialState;
        private int spotBlank;
        private int seqNumber;
        private int radialNumber;
        private int elevationNumber;
        private double azimuth;
        private double elevation;
        private int seconds;
        private int microseconds;
        private int dataLength;
        private int momentNumber;
        private int res5;
        private int horiEstNoise;
        private int vertEstNoise;
        private String zipType;
        private String res6;

        public void parse(byte[] b) throws Exception {
            int index = 0;
            radialState = BitConverter.toInt32(b, index);
            index += 4;
            spotBlank = BitConverter.toInt32(b, index);
            index += 4;
            seqNumber = BitConverter.toInt32(b, index);
            index += 4;
            radialNumber = BitConverter.toInt32(b, index);
            index += 4;
            elevationNumber = BitConverter.toInt32(b, index);
            index += 4;

            azimuth = BitConverter.toSingle(b, index);
            index += 4;
            elevation = BitConverter.toSingle(b, index);
            index += 4;

            seconds = BitConverter.toInt32(b, index);
            index += 4;
            microseconds = BitConverter.toInt32(b, index);
            index += 4;
            dataLength = BitConverter.toInt32(b, index);
            index += 4;
            momentNumber = BitConverter.toInt32(b, index);
            index += 4;

            res5 = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            horiEstNoise = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            vertEstNoise = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            zipType = BitConverter.toString(Arrays.copyOfRange(b, index, index + 1));
            index += 1;
            zipType = zipType.replace("\u0000", "");
            res6 = BitConverter.toString(Arrays.copyOfRange(b, index, index + 13));
            index += 13;
        }
    }

    public class MomentHeader {
        private int len = 32;
        private int dataType;
        private int scale;
        private int offset;
        private int binLength;
        private int flags;
        private int blockLength;
        private String res;

        public void parse(byte[] b) throws Exception {
            int index = 0;
            dataType = BitConverter.toInt32(b, index);
            index += 4;
            scale = BitConverter.toInt32(b, index);
            index += 4;
            offset = BitConverter.toInt32(b, index);
            index += 4;
            binLength = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            flags = (BitConverter.toInt16(b, index) & 0x0FFFF);
            index += 2;
            blockLength = BitConverter.toInt32(b, index);
            index += 4;

            res = BitConverter.toString(Arrays.copyOfRange(b, index, index + 12));
            index += 12;
        }
    }
    //endregion
}
